﻿using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

namespace SpiritEngine
{
    // An enum of buttons on he mouse, since XNA doesn't provide one
    public enum MouseButtons { Left, Right, Middle, X1, X2 };

    // An input device that keeps track of the Mouse and MouseState
    public class CID_Mouse : AC_InputDevice<MouseState>
    {
        // MEMBER VARIABLES
        // ------------------------------------------------------------------------------------------------------------------------

        Engine m_Engine = null; // A reference to the instance of the engine we are using


        // The last and current MouseStates
        MouseState last;
        MouseState current;

        // The MouseButtons that are currently down
        MouseButtons[] currentButtons;

        // Position of the mouse
        Vector2 position;

        // The change in position of the mouse between the last two frames
        public Vector2 Delta = Vector2.Zero;

        // Whether to reset the mouse to the middle of the screen on each update to simulate
        // a screen with no borders
        public bool ResetMouseAfterUpdate = true;

        // The change in position of the scroll wheel between the last two frames
        public float ScrollDelta = 0;
        
        // List of pressed buttons used to setup currentButtons
        List<MouseButtons> pressed = new List<MouseButtons>();


        // CONSTRUCTORS
        // ------------------------------------------------------------------------------------------------------------------------

        // Constructor gets the initial MouseState, moves the mouse to the center of the screen,
        // and does the first update
        public CID_Mouse(Engine engine)
        {
            bool bIsGameActive = false; 

            m_Engine = engine;


            if (m_Engine.Game != null)
            {
                if (m_Engine.Game.Window.ClientBounds.X > 0)
                    bIsGameActive = true;   // The game window is the active window so we set this flag variable to true.
            }


            if (ResetMouseAfterUpdate && bIsGameActive)
            {
                Position = new Vector2(m_Engine.GraphicsDevice.Viewport.Width / 2,
                                       m_Engine.GraphicsDevice.Viewport.Height / 2);
            }

            current = Mouse.GetState();
            Update();

        } // end CID_Mouse.CID_Mouse()



        // PRIVATE/PROTECTED METHODS
        // ------------------------------------------------------------------------------------------------------------------------

        // Retrieves the ButtonState of the specified mouse button from the specified MouseState
        ButtonState GetButtonState(MouseButtons Button, MouseState State)
        {
            if (Button == MouseButtons.Left)
                return State.LeftButton;
            else if (Button == MouseButtons.Middle)
                return State.MiddleButton;
            else if (Button == MouseButtons.Right)
                return State.RightButton;
            else if (Button == MouseButtons.X1)
                return State.XButton1;
            else if (Button == MouseButtons.X2)
                return State.XButton2;


            return ButtonState.Released;

        } // end CID_Mouse.GetButtonState(MouseButtons, MouseState)

        // An internal version of IsButtonDown that also allows us to specify
        // which state to check against
        bool IsButtonDown(MouseButtons Button, MouseState state)
        {
            return GetButtonState(Button, State) == ButtonState.Pressed ? true : false;

        } // end CID_Mouse.IsButtonDown(MouseButtons, MouseState)

        // An internal version of IsButtonUp that also allows us to specify
        // which state to check against
        bool IsButtonUp(MouseButtons Button, MouseState State)
        {
            return GetButtonState(Button, State) == ButtonState.Released ? true : false;

        } // end CID_Mouse.IsButtonUp(MouseButtons, MouseState)



        // METHODS
        // ------------------------------------------------------------------------------------------------------------------------

        public override void Update()
        {
            // Update the last state
            last = current;

            // Extract the scroll wheel position from the last state
            float lastScrollWheel = last.ScrollWheelValue;

            // Update the current state and clear the list of pressed buttons
            current = Mouse.GetState();
            pressed.Clear();

            // Calculate the scroll delta with the current position and the copy
            // of the last position we made earlier
            ScrollDelta = ScrollPosition - lastScrollWheel;

            // If we are supposed to reset the mouse after each update...
            if (ResetMouseAfterUpdate)
            {
                // Find the center of the screen
                Vector2 center = new Vector2(m_Engine.GraphicsDevice.Viewport.Width / 2,
                                             m_Engine.GraphicsDevice.Viewport.Height / 2);

                // Find the mouse's distance from the center of the screen to find
                // the change in position
                Delta = new Vector2(current.X - center.X,
                                    current.Y - center.Y);

                // Move the mouse back to the center so we never run out of screen space
                Position = center;
            }
            else
            {
                // Otherwise just find the difference in position between the last two frames
                Delta = new Vector2(current.X - last.X,
                                    current.Y - last.Y);
            }


            // For each mouse button...
            foreach (MouseButtons button in Utility.GetEnumValues<MouseButtons>())
            {
                // If it is down, add it to the list
                if (IsButtonDown(button))
                    pressed.Add(button);

                // If it was just pressed, fire the corresponding event
                if (WasButtonPressed(button))
                {
                    if (ButtonPressed != null)
                        ButtonPressed(this, new InputDeviceEventArgs<MouseButtons, MouseState>(button, this));
                }

                // If it was just released, fire the corresponding event
                if (WasButtonReleased(button))
                {
                    if (ButtonReleased != null)
                        ButtonReleased(this, new InputDeviceEventArgs<MouseButtons, MouseState>(button, this));
                }

                // If it was held, fire the corresponding event
                if (WasButtonHeld(button))
                {
                    if (ButtonHeld != null)
                        ButtonHeld(this, new InputDeviceEventArgs<MouseButtons,MouseState>(button, this));
                }
            } // end foreach

            // Update the currentButtons array from the list of buttons that are currently down
            currentButtons = pressed.ToArray();

        } // end CID_Mouse.Update()

        // Whether the specified button is currently down
        public bool IsButtonDown(MouseButtons Button)
        {
            return IsButtonDown(Button, current);

        } // end CID_Mouse.IsButtonDown(MouseButtons)

        // Whether the specified button is currently up
        public bool IsButtonUp(MouseButtons Button)
        {
            return IsButtonUp(Button, current);

        } // end CID_Mouse.IsButtonUp(MouseButtons)

        // Whether the specified button is down for the first time this frame
        public bool WasButtonPressed(MouseButtons Button)
        {
            if (IsButtonUp(Button, last) && IsButtonDown(Button, current))
                return true;

            return false;

        } // end CID_Mouse.WasButtonPressed(MouseButtons)

        // Whether the specified button is up for the first time this frame
        public bool WasButtonReleased(MouseButtons Button)
        {
            if (IsButtonDown(Button, last) && IsButtonUp(Button, current))
                return true;

            return false;

        } // end CID_Mouse.WasButtonReleased(MouseButtons)

        // Whether the specified button has been down for more than one frame
        public bool WasButtonHeld(MouseButtons Button)
        {
            if (IsButtonDown(Button, last) && IsButtonDown(Button, current))
                return true;

            return false;

        } // end CID_Mouse.WasButtonHeld(MouseButtons)


        // DEBUG METHODS
        // ------------------------------------------------------------------------------------------------------------------------

#if (DEBUG)
        // put all debug methods in here...
#endif


        // PROPERTIES
        // ------------------------------------------------------------------------------------------------------------------------

        // State property
        public override MouseState State
        {
            get
            {
                return current;
            }
        } // end property State

        // PressedButtons property
        public MouseButtons[] PressedButtons
        {
            get
            {
                return currentButtons;
            }
        } // end property PressedButtons

        // Position property
        public Vector2 Position
        {
            get
            {
                return position;
            }
            set
            {
                position = value;
                Mouse.SetPosition( (int) value.X, (int) value.Y);
            }
        } // end property Position

        // ScrollPosition property
        // The current position of the mouse's scroll wheel
        public float ScrollPosition
        {
            get
            {
                return current.ScrollWheelValue;
            }
        } // end property ScrollPosition



        // EVENTS
        // ------------------------------------------------------------------------------------------------------------------------

        // Events for when a button is pressed, released, or held
        public event InputEventHandler<MouseButtons, MouseState> ButtonPressed;
        public event InputEventHandler<MouseButtons, MouseState> ButtonReleased;
        public event InputEventHandler<MouseButtons, MouseState> ButtonHeld;



    } // end class CID_Mouse

} // end namespace SpiritEngine
